<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
</body>
<script>
  let activeEffect
  const effectStack = []
  const jobQueue = new Set()
  const p = Promise.resolve()
  let isFlushing = false
  const bucket = new WeakMap()
  const data = { foo: 1 , bar: { boo: 2 } }
  const IERATE_KEY = Symbol()
  const MAP_KEY_IERATE_KEY = Symbol()
  const reactiveMap = new Map()

  function ref(val) {
    const wrapper = {
      value: val
    }
    Object.defineProperty(wrapper, '_v_isRef', {
      value: true
    })
    return reactive(wrapper)
  }

  function toRef(obj, key) {
    const wrapper = {
      get value() {
        return obj[key]
      },
      set value(val) {
        obj[key] = val
      }
    }
    Object.defineProperty(wrapper, '_v_isRef', {
      value: true
    })
    return wrapper
  }

  function toRefs(obj) {
    const ret = {}
    for (const key in obj) {
      ret[key] = toRef(obj, key)
    }
    return ret
  }

  function proxyRefs(target) {
    return new Proxy(target, {
      get(target, key, receiver) {
        const value = Reflect.get(target, key, receiver)
        return value._v_isRef ? value.value : value
      },
      set(target, key, newVal, receiver) {
        const value = target[key]
        if (value._v_isRef) {
          value.value = newVal
          return true
        }
        return Reflect.set(target, key, newVal, receiver)
      }
    })
  }

  function reactive(obj) {
    const existion = reactiveMap.get(obj)
    if (existion) return existion
    const proxy = createReactive(obj)
    reactiveMap.set(obj, proxy)
    return proxy
  }

  function shallowReactive(obj) {
    return createReactive(obj, true)
  }

  function readonly(obj) {
    return createReactive(obj, false, true)
  }

  function shallowReadonly(obj) {
    return createReactive(obj, true, true)
  }

  const originMethod = Array.prototype.includes
  const arrayInstrumentations = {}

  ;['includes','indexOf','lastIndexOf'].forEach(method => {
    const originMethod = Array.prototype[method]
    arrayInstrumentations[method] = function (...args) {
      let res = originMethod.apply(this, args)
      if (res === false || res === -1) {
        res = originMethod.apply(this.raw, args)
      }
      return res
    }
  })

  let shouldTrack = true
  ;['push','pop','shift','unshift','splice'].forEach(method => {
    const originMethod = Array.prototype[method]
    arrayInstrumentations[method] = function (...args) {
      shouldTrack = false
      let res = originMethod.apply(this, args)
      shouldTrack = true
      return res
    }
  })

  const mutableInstrumentations = {
    add(key) {
      const target = this.raw
      const hadKey = target.has(key)
      const res = target.add(key)
      if (!hadKey) {
        trigger(target, key, 'ADD')
      }
      return res
    },
    delete(key) {
      const target = this.raw
      const hadKey = target.has(key)
      const res = target.delete(key)
      if (hadKey) {
        trigger(target, key, 'DELETE')
      }
      return res
    },
    get(key) {
      const target = this.raw
      const had = target.has(key)
      track(target, key)
      if (had) {
        const res = target.get(key)
        return typeof res === 'object' ? reactive(res) : res
      }
    },
    set(key, value) {
      const target = this.raw
      const had = target.has(key)
      const oldValue = target.get(key)
      // 如果新值是代理数据，则返回原始数据
      const rawValue = value.raw || value
      target.set(key, rawValue)
      if (!had) {
        trigger(target, key, 'ADD')
      }else if(oldValue !== value && (oldValue === oldValue || value === value)){
        trigger(target, key, 'SET')
      }
    },
    forEach(callback) {
      const wrap = (val) => typeof val === 'object' ? reactive(val) : val
      const target = this.raw
      track(target, IERATE_KEY)
      target.forEach((v, k) => {
        callback.call(thisArg, wrap(v), wrap(k), this)
      })
    },
    [Symbol.iterator]: iterationMethod,
    entries: iterationMethod,
    values: valuesIterationMethod,
    keys: keysIterationMethod,
  }

  function createReactive(obj, isShallow = false, isReadOnly = false) {
    return new Proxy(obj, {
      get(target, key, receiver) {
        // set对象的size属性是一个适配器属性，它的getter会去寻找内部[[setdata]]方法，代理对象内部没有此方法需要手动改变this
        if (key === 'size') {
          track(target, IERATE_KEY)
          return Reflect.get(target, key, target)
        }
        // 代理对象可以通过raw查看原始对象
        if (key === 'raw') {
          return target
        }
        // 调用重写后的数组方法
        if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
          return Reflect.get(arrayInstrumentations, key, receiver)
        }
        const res = Reflect.get(target, key, receiver)
        if (!isReadOnly && typeof key !== 'symbol') {
          track(target, key) 
        }
        if (isShallow) {
          return res
        }
        if (typeof res === 'object' && res !== null) {
          return isReadOnly ? readonly(res) : reactive(res)
        }
        // 例 set的delete方法需要手动指向原始对象this
        // return target[key].bind(target)
        if (!mutableInstrumentations[key]) {
          return res
        }
        return mutableInstrumentations[key]
      },
      set(target, key, newVal, receiver) {
        if (isReadOnly) {
          console.warn(`属性${key}是只读的`);
          return true
        }
        const oldValue = target[key]
        const type = Array.isArray(target)
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
        const res = Reflect.set(target, key, newVal, receiver)
        if (receiver.raw === target) {
          if (oldValue !== newVal && (oldValue === oldValue || newVal === newVal)) {
            trigger(target, key, type, newVal)  
          }
        }
        return res
      },
      deleteProperty(target, key) {
        if (isReadOnly) {
          console.warn(`属性${key}是只读的`);
          return true
        }
        const hadKey = Object.prototype.hasOwnProperty.call(target, key)
        const res = Reflect.deleteProperty(target, key)
        if (res && hadKey) {
          trigger(target, key, 'DELETE')
        }
        return res
      },
      // 拦截in操作符
      has(target, key) {
        track(target, key)
        return Reflect.has(target, key)
      },
      // 拦截for...in 循环
      ownKeys(target) {
        track(target, Array.isArray(target) ? 'length' : IERATE_KEY)
        return Reflect.ownKeys(target)
      }
    })
  }
  const arr = reactive(new Map([['key', 'value']]))

  const p2 = reactive(new Map([['k1', 'v1'],['k2', 'v2']]))

  const obj = reactive({foo: 1, bar: 2})
  const newObj = proxyRefs({...toRefs(obj)})

  effect(() => {
    console.log(newObj.bar);
  })
  obj.bar = 3
  // effect(() => {
  //   arr.push(1)
  // })

  // effect(() => {
  //   arr.push(1)
  // })
  
  setTimeout(() => {
    
  },1000)

  function track(target, key) {
    if (!activeEffect || !shouldTrack) {
      return
    }
    let depsMap = bucket.get(target)
    if (!depsMap) {
      bucket.set(target, (depsMap = new Map()))
    }
    let deps = depsMap.get(key)
    if (!deps) {
      depsMap.set(key, (deps = new Set()))
    }
    deps.add(activeEffect)
    activeEffect.deps.push(deps)
  }

  function trigger(target, key, type, newVal) {
    const depsMap = bucket.get(target)
    if (!depsMap) {
      return
    }
    const effects = depsMap.get(key)

    const effectsToRun = new Set()
    effects && effects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
    if ((type === 'ADD' || type === 'DELETE') && (Object.prototype.toString.call(target) === '[object Map]')) {
      const iterateEffects = depsMap.get(MAP_KEY_IERATE_KEY)
      iterateEffects && iterateEffects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
    }
    if (type === 'ADD' && Array.isArray(target)) {
      const lengthEffects = depsMap.get('length')
      lengthEffects && lengthEffects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
    }
    if (Array.isArray(target) && key === 'length') {
      depsMap.forEach((effects, key) => {
        if (key >= newVal) {
          effects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
              effectsToRun.add(effectFn)
            }
          })
        }
      })
    }
    effectsToRun.forEach(effectFn => {
      if (effectFn.options.scheduler) {
        effectFn.options.scheduler(effectFn)
      }else{
        effectFn()
      }
    })
  }

  function effect(fn, options = {}) {
    const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn)
      const res = fn()
      effectStack.pop()
      activeEffect = effectStack[effectStack.length - 1]
      return res
    }
    effectFn.deps = []
    effectFn.options = options
    if (!options.lazy) {
      effectFn()
    }
    return effectFn
  }

  function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
      const deps = effectFn.deps[i]
      deps.delete(effectFn)
    }
    effectFn.deps.length = 0
  }

  // entry与[symbol.iterator]的复用逻辑
  function iterationMethod() {
    const target = this.raw
    const itr = target[Symbol.iterator]()
    const wrap = (val) => typeof val === 'object' ? reactive(val) : val
    track(target, IERATE_KEY)
    return {
      next() {
        const { value, done } = itr.next()
        return {
          value : value ? [wrap(value[0]), wrap(value[1])] : value,
          done
        }
      },
      [Symbol.iterator]() {
        return this
      }
    }
  }

  function valuesIterationMethod() {
    const target = this.raw
    const itr = target.values()
    const wrap = (val) => typeof val === 'object' ? reactive(val) : val
    track(target, IERATE_KEY)
    return {
      next() {
        const { value, done } = itr.next()
        return {
          value : wrap(value),
          done
        }
      },
      [Symbol.iterator]() {
        return this
      }
    }
  }

  function keysIterationMethod() {
    const target = this.raw
    const itr = target.keys()
    const wrap = (val) => typeof val === 'object' ? reactive(val) : val
    track(target, MAP_KEY_IERATE_KEY)
    return {
      next() {
        const { value, done } = itr.next()
        return {
          value : wrap(value),
          done
        }
      },
      [Symbol.iterator]() {
        return this
      }
    }
  }

  // watch
  function watch(source, cb, options = {}) {
    let getter
    let oldValue, newValue
    if (typeof source === 'function') {
      getter = source
    }else{
      getter = () => traverse(source)
    }
    const job = () => {
      newValue = effectFn()
      cb(newValue, oldValue)
      oldValue = newValue
    }
    const effectFn = effect(
      () => getter(),
      {
        lazy: true,
        scheduler: () => {
          if (options.post) {
            const p = Promise.resolve()
            p.then(job)
          }else{
            job()
          }
        }
      }
    )
    if (options.immediate) {
      job()
    }else{
      oldValue = effectFn()
    }
  }

  function traverse(value, seen = new Set()) {
    if (typeof value !== 'object' || value === null || seen.has(value)) {
      return
    }
    seen.add(value)
    for (const k in value) {
      traverse(value[k])
    }
    return value
  }

  // watch(() => obj.foo, (newValue, oldValue) => {
  //   console.log(newValue);
  //   console.log(oldValue);
  // },{
  //   immediate: true
  // })
  // obj.foo++

  // 计算属性
  function computed(getter) {
    let value
    let dirty = true
    const effectFn = effect(getter, {
      lazy: true,
      scheduler() {
        if (!dirty) {
          dirty = true
          trigger(obj, 'value')
        }
      }
    })
    const obj = {
      get value() {
        if (dirty) {
          value = effectFn()
          dirty = false
        }
        track(obj, 'value')
        return value
      }
    }
    return obj
  }

  // const sumRes = computed(() => obj.foo + obj.bar)
  // 过渡状态
  // effect(() => {
  //   console.log(obj.foo);
  // },{
  //     scheduler(fn) {
  //       jobQueue.add(fn)
  //       flushJob()
  //     }
  // })
  function flushJob() {
    if (isFlushing) {
      return
    }
    isFlushing = true
    p.then(() => {
      jobQueue.forEach(job => job())
    }).finally(() => {
      isFlushing = false
    })
  }

</script>
</html>